﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;

namespace WinRTFramework.Extensions
{
	public static class FrameworkElementExtensions
	{
		public static void RegisterDependencyPropertyChanged<T>( this FrameworkElement element, Expression<Func<T>> dependencyPropertyFunc,
																	Action<DependencyPropertyChangedEventArgs> changedCallback )
		{
			FrameworkElementAttachedProperties.RegisterDependencyPropertyBinding( element, dependencyPropertyFunc, changedCallback );
		}
	}

	internal class FrameworkElementAttachedProperties : DependencyObject
	{
		private const string _extensionDPsNamePrefix = "DependencyPropertyEx";
		private static readonly Dictionary<string, DependencyProperty> _staticExtensionDPs = new Dictionary<string, DependencyProperty>();

		#region Properties
		#region DependencyPropertyCallbacks (Attached Property)
		public static readonly DependencyProperty DependencyPropertyCallbacksProperty =
		  DependencyProperty.RegisterAttached( "DependencyPropertyCallbacks", typeof( DependencyPropertyChangedCallbacks ), typeof( FrameworkElementAttachedProperties ),
											  new PropertyMetadata( null ) );

		public static void SetDependencyPropertyCallbacks( DependencyObject d, DependencyPropertyChangedCallbacks value )
		{
			d.SetValue( DependencyPropertyCallbacksProperty, value );
		}

		public static DependencyPropertyChangedCallbacks GetDependencyPropertyCallbacks( DependencyObject d )
		{
			return (DependencyPropertyChangedCallbacks)d.GetValue( DependencyPropertyCallbacksProperty );
		}
		#endregion
		#endregion

		public static void RegisterDependencyPropertyBinding<T>( FrameworkElement element, Expression<Func<T>> dependencyPropertyFunc, Action<DependencyPropertyChangedEventArgs> changedCallback )
		{
			string propertyName = GetPropertyName( dependencyPropertyFunc );
			DependencyPropertyChangedCallbacks callbacks = GetCallbacksForElement( element );
			DependencyProperty attachedDPToBindTo = GetNextUnusedAttachedPropertyForFrameworkElement(propertyName);
			callbacks.Add( attachedDPToBindTo, changedCallback );

			RegisterDependencyPropertyBinding( element, propertyName, attachedDPToBindTo );
		}

		private static void RegisterDependencyPropertyBinding( FrameworkElement element, string dependencyPropertyToBind, DependencyProperty attachedDp )
		{
			var dependencyPropertyExtensionBinding = new Binding
			{
				Path = new PropertyPath( dependencyPropertyToBind ),
				RelativeSource = new RelativeSource
				{
					Mode = RelativeSourceMode.Self
				}
			};
			element.SetBinding( attachedDp, dependencyPropertyExtensionBinding );
		}

		private static DependencyPropertyChangedCallbacks GetCallbacksForElement( FrameworkElement element )
		{
			DependencyPropertyChangedCallbacks callbacks = GetDependencyPropertyCallbacks( element );
			if ( callbacks == null )
			{
				callbacks = new DependencyPropertyChangedCallbacks();
				SetDependencyPropertyCallbacks( element, callbacks );
			}

			return callbacks;
		}

		private static void DependencyPropertyExPropertyChanged( object sender, DependencyPropertyChangedEventArgs e )
		{
			Action<DependencyPropertyChangedEventArgs> callback = TryGetCallback( (FrameworkElement)sender, e.Property );
			if ( callback != null )
			{
				callback( e );
			}
		}

		private static DependencyProperty GetNextUnusedAttachedPropertyForFrameworkElement( string propertyName )
		{
            if (!_staticExtensionDPs.ContainsKey(propertyName))
            {
                var unusedDependencyProperty = DependencyProperty.RegisterAttached(_extensionDPsNamePrefix + "_" + propertyName,
                                                               typeof(object),
                                                               typeof(FrameworkElementAttachedProperties),
                                                               new PropertyMetadata(null, DependencyPropertyExPropertyChanged));
                _staticExtensionDPs.Add(propertyName, unusedDependencyProperty);
            }

		    return _staticExtensionDPs[propertyName];
		}

		private static Action<DependencyPropertyChangedEventArgs> TryGetCallback( FrameworkElement element, DependencyProperty boundAttachedDP )
		{
			Action<DependencyPropertyChangedEventArgs> callback = null;
			DependencyPropertyChangedCallbacks callbacks = GetDependencyPropertyCallbacks( element );
			if ( callbacks != null )
			{
				callbacks.TryGetValue( boundAttachedDP, out callback );
			}

			return callback;
		}

		private static string GetPropertyName<T>( Expression<Func<T>> exp )
		{
			return ( ( (MemberExpression)( exp.Body ) ).Member ).Name;
		}
	}

	internal class DependencyPropertyChangedCallbacks : Dictionary<DependencyProperty, Action<DependencyPropertyChangedEventArgs>>
	{
	}
}